import {Block} from "./block";
import {FileLineReader} from "./file-line-reader";
import {Record, RecordCategory, RecordType} from "./record";
import {MemoryPool} from "./memory-pool";
import {EventDiagnose} from "./diagnose/event-diagnose.js";

export class ProfilingData {
    constructor() {
        this.clear();
    }

    clear() {
        this.records = [];
        this.clearParsedData();
    }

    clearParsedData() {
        this.rtStreamAddressesToStreamId = new Map();

        this.blockIdsToBlock = [];
        this.streamIdsToMemporyPool = new Map();
        this.addressesToLastBlock = new Map();

        this.firstLogTimestamp = Number.MAX_VALUE;
        this.latestLogTimestamp = 0;
        this.needRefreshParseResults = false;

        this.eventRecords = []

        this.diagnosticTools = [
            new EventDiagnose()
        ]
    }

    addLogFile(logFile, progressCallback) {
        console.log("Begin to read in log file", logFile.name, ", file size ", logFile.size);
        let pd = this;
        return new Promise((resolve, reject) => {
            new FileLineReader(logFile, (line, proc) => {
                if (line === null) {
                    if (pd.needRefreshParseResults) {
                        // todo reparse可能很耗时，要考虑分片做
                        pd.reParseAllRecords();
                        pd.needRefreshParseResults = false;
                    }
                    resolve(pd);
                    return;
                }
                if (progressCallback) {
                    progressCallback(proc);
                }
                let rec = Record.parseFromLog(line, pd);
                if (rec === null) {
                    return;
                }
                pd.addRecord(rec);
            }).read();
        })
    }

    addRecord(rec) {
        this.records.push(rec);
        if (this.needRefreshParseResults) {
            // 如果本次导入后，需要重新刷新整个blocks结果，那么没必要做接下来的rec解析了
            return;
        }
        if (this.latestLogTimestamp > rec.timestamp) {
            // log文件没有按照时间先后导入，或者log文件内的时间出现错乱，需要重新生成blocks,以免因为allocMem出现错位导致block创建过晚
            this.needRefreshParseResults = true;
            return;
        }
        this.latestLogTimestamp = rec.timestamp;
        if (this.firstLogTimestamp > rec.timestamp) {
            this.firstLogTimestamp = rec.timestamp;
        }
        this.parseRecord(rec);
    }

    parseRecord(rec) {
        for (let diagTool of this.diagnosticTools) {
            diagTool.addRecord(rec);
        }
        if (rec.type === RecordType.linkStream) {
            this.addLinkStreamRecord(rec);
            return;
        }
        if (rec.address === "(nil)") {
            console.log("WARNING: Drop log with nil address.", rec);
            return;
        }

        switch (rec.category) {
            case RecordCategory.Memory: {
                if (rec.type !== RecordType.allocMem) {
                    let block = this.getLastBlock(rec.address);
                    if (block === undefined) {
                        console.log("WARNING: The address in record can not be recognized, drop it.", rec);
                        return;
                    }
                    block.addRecord(rec);
                } else {
                    this.createNewBlock(rec.address).addRecord(rec);
                }
                this.getOrCreateMemoryPool(rec.stream).addRecord(rec);
            }
                break;
            case RecordCategory.Pool:
                this.getOrCreateMemoryPool(rec.stream).addRecord(rec);
                break;
            case RecordCategory.Event:
                this.eventRecords.push(rec);
                break;
            default:
                break;
        }
    }

    addLinkStreamRecord(rec) {
        this.rtStreamAddressesToStreamId.set(rec.stream, rec.streamId);
    }

    getOrCreateMemoryPool(streamId) {
        let pool = this.streamIdsToMemporyPool.get(streamId)
        if (pool === undefined) {
            pool = new MemoryPool(streamId, this.addressesToLastBlock);
            this.streamIdsToMemporyPool.set(streamId, pool);
        }
        return pool;
    }

    createNewBlock(address) {
        let block = new Block(this.blockIdsToBlock.length);
        this.blockIdsToBlock.push(block);
        this.addressesToLastBlock.set(address, block);
        return block;
    }

    getLastBlock(address) {
        return this.addressesToLastBlock.get(address);
    }

    reParseAllRecords() {
        this.clearParsedData();
        this.records.sort((a, b) => {
            if (a.timestamp < b.timestamp) {
                return -1;
            }
            if (a.timestamp > b.timestamp) {
                return 1;
            }
            return 0;
        });
        for (let rec of this.records) {
            this.parseRecord(rec);
        }
    }

    getDiagErrors() {
        let ret = [];
        for (let diagTool of this.diagnosticTools) {
            ret.push(...diagTool.getErrors());
        }
        return ret;
    }
}
